package com.wxhandle.cleandemo.sso.config;

import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
// import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
// import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import java.util.Arrays;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;

import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.wxhandle.cleandemo.sso.jose.Jwks;
import com.wxhandle.cleandemo.sso.oauth2.authentication.OAuth2ResourceOwnerPasswordAuthenticationConverter;
import com.wxhandle.cleandemo.sso.oauth2.authentication.OAuth2ResourceOwnerPasswordAuthenticationProvider;
import com.wxhandle.cleandemo.sso.oauth2.customizer.jwt.JwtCustomizer;
import com.wxhandle.cleandemo.sso.oauth2.customizer.jwt.JwtCustomizerHandler;
import com.wxhandle.cleandemo.sso.oauth2.customizer.jwt.impl.JwtCustomizerImpl;
import com.wxhandle.cleandemo.sso.oauth2.customizer.token.claims.OAuth2TokenClaimsCustomizer;
import com.wxhandle.cleandemo.sso.oauth2.customizer.token.claims.impl.OAuth2TokenClaimsCustomizerImpl;

import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {

	// @Autowired
	// private JwtCustomizer jwtCustomizer;

	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
		OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
				new OAuth2AuthorizationServerConfigurer<>();

		http.apply(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> tokenEndpoint.accessTokenRequestConverter(
			new DelegatingAuthenticationConverter(Arrays.asList(
				new OAuth2AuthorizationCodeAuthenticationConverter(),
				new OAuth2RefreshTokenAuthenticationConverter(),
				new OAuth2ClientCredentialsAuthenticationConverter(),
				new OAuth2ResourceOwnerPasswordAuthenticationConverter()))
		)));

		RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

		http
			.requestMatcher(endpointsMatcher)
			.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
			.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
			.apply(authorizationServerConfigurer);
		SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();

		addCustomOAuth2ResourceOwnerPasswordAuthenticationProvider(http);
		return securityFilterChain;
	}

	// @formatter:off
	// @Bean
	// public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
	// 	RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
	// 			.clientId("password-messaging-client")
	// 			.clientSecret("secret3")
	// 			.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
	// 			.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
	// 			.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
	// 			.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
	// 			.authorizationGrantType(AuthorizationGrantType.PASSWORD)
	// 			.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
	// 			.redirectUri("http://127.0.0.1:8080/authorized")
	// 			.scope(OidcScopes.OPENID)
	// 			.scope("message.read")
	// 			.scope("message.write")
	// 			.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
	// 			.build();

	// 	// Save registered client in db as if in-memory
	// 	JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
	// 	registeredClientRepository.save(registeredClient);

	// 	return registeredClientRepository;
	// }


	@Bean
	public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
	}

	@Bean
	public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
	}

	@Bean
	public JWKSource<SecurityContext> jwkSource() {
		RSAKey rsaKey = Jwks.generateRsa();
		JWKSet jwkSet = new JWKSet(rsaKey);
		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
	}


	@Bean
	public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
		return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
	}

	@Bean
	public ProviderSettings providerSettings() {
		return ProviderSettings.builder().issuer("http://auth-server:9000").build();
	}

	// @Bean
	// public EmbeddedDatabase embeddedDatabase() {
	// 	// @formatter:off
	// 	return new EmbeddedDatabaseBuilder()
	// 			.generateUniqueName(true)
	// 			.setType(EmbeddedDatabaseType.H2)
	// 			.setScriptEncoding("UTF-8")
	// 			.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
	// 			.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
	// 			.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
	// 			.build();
	// 	// @formatter:on
	// }

	@Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> buildJwtCustomizer() {

		JwtCustomizerHandler jwtCustomizerHandler = JwtCustomizerHandler.getJwtCustomizerHandler();
		JwtCustomizer jwtCustomizer = new JwtCustomizerImpl(jwtCustomizerHandler);
        OAuth2TokenCustomizer<JwtEncodingContext> customizer = (context) -> {
        	jwtCustomizer.customizeToken(context);
        };

        return customizer;
    }
	// @Bean
    // public OAuth2TokenCustomizer<JwtEncodingContext> buildCustomizer() {
    //     OAuth2TokenCustomizer<JwtEncodingContext> customizer = (context) -> {
    //     	jwtCustomizer.customizeToken(context);
    //     };

    //     return customizer;
    // }


	@Bean
    public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> buildOAuth2TokenClaimsCustomizer() {

		OAuth2TokenClaimsCustomizer oauth2TokenClaimsCustomizer = new OAuth2TokenClaimsCustomizerImpl();
        OAuth2TokenCustomizer<OAuth2TokenClaimsContext> customizer = (context) -> {
        	oauth2TokenClaimsCustomizer.customizeTokenClaims(context);
        };

        return customizer;
    }

	@SuppressWarnings("unchecked")
	private void addCustomOAuth2ResourceOwnerPasswordAuthenticationProvider(HttpSecurity http) {

		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
		OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);

		OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider =
				new OAuth2ResourceOwnerPasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator);

		// This will add new authentication provider in the list of existing authentication providers.
		http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);

	}
}

